//手写MyPromise

//设置Promise状态
const PENDING = "pending" //等待
const FULFILLED = "fulfilled" //完成
const REJECTED = "rejected" //失败
class MyPromise{
    //接收回调函数
    constructor(executor){
        //捕获执行器错误
        try{
            //立即执行回调函数,因为Promise的构造过程是同步的
            //传入resolve和reject,不传入外面获取不到，并把resolve和reject写作实例方法加this
            executor(this.resolve,this.reject)
        }catch(e){
            this.reject(e)
        }
    }
    //MyPromise实例的状态,给个默认值是pending,状态都用全局常数表示，需要定义全局常数变量
    status = PENDING
    //resolve的参数
    value = undefined
    //reject的参数
    reason = undefined
    //成功的回调函数,成功的回调改成数组，接收多次回调，先进先出
    sucsessCallBack = []
    //失败的回调函数,失败的回调函数不用是数组，Promise产生多次异常只会返回第一个
    failCallBack = undefined
    //resolve方法,resolve方法要用箭头函数，因为调用时是直接写resolve()调用，作用域不能指向全局
    //resolve接收参数value,缓存到对象中this.value
    resolve = (value) => {
        //使状态改变不可逆
        if(this.status !== PENDING){
            return
        }
        //一旦调用了resolve方法，status就要变成fulfilled
        this.status = FULFILLED
        //兑现的值缓存到对象中
        this.value = value
        //成功时如果成功函数存在，执行缓存的成功函数
        // this.sucsessCallBack && this.sucsessCallBack(this.value)
        //因为是成功回调改成数组所以需要while循环，this.value再then方法中赋过值了，所以在这里直接执行就好
        while(this.sucsessCallBack.length) this.sucsessCallBack.shift()()
    }
    //reject方法,reject方法要用箭头函数，同上
    reject = (reason) => {
        //使状态改变不可逆
        if(this.status !== PENDING){
            return
        }
        //一旦调用了reject方法，status就要变成rejected
        this.status = REJECTED
        //拒绝的值缓存到对象中
        this.reason = reason
        //失败时如果失败函数存在，执行缓存的失败函数
        this.failCallBack && this.failCallBack(this.reason)
        // while(this.failCallBack.length) this.failCallBack.shift()()
    }
    //定义then方法,then方法接收两个回调函数,第一个成功回调，第二个失败回调
    then(sucsessCallBack,failCallBack){
        //使参数可选，现有sucsessCallBack，才会有failCallBack，所以先判断sucsessCallBack
        //如果sucsessCallBack存在就取sucsessCallBack，否则给个默认传参的方法
        sucsessCallBack = sucsessCallBack?sucsessCallBack:value => value
        //同上，但默认方法要抛出异常，throw抛出异常后才会被try/catch捕获走reject
        failCallBack = failCallBack?failCallBack:reason => { throw reason }
        //链式调用需要返回一个新的MyPromise对象
        let promise1 = new MyPromise((resolve, reject)=>{
            //将逻辑放入构造函数中立即执行
            //如果状态是成功，就调用成功回调
            if(this.status === FULFILLED){
                //成功时传入value
                setTimeout(()=>{
                    //捕获回调函数中的异常并处理，try/catch要放在setTimeout中，否则放在外面，try/catch是同步会先执行
                    //捕获不到异步函数的异常
                    try{
                        //链式调用，需要then返回一个新的MyPromise,这个地方一定要放到异步函数里面，
                        //回调函数返回的值在外面就是return的值，这个值要在生成了MyPromise对象之后再获取，里面外面都一样
                        let params = sucsessCallBack(this.value)
                        //把解决和拒绝封装成方法,传入4个参数,因为promise还没有生成，不能提前调用，所以要使用异步方法
                        resolveMyPromise(promise1,params,resolve,reject)
                    }catch(e){
                        //拒绝异常信息，下同
                        reject(e)
                    }
                })
            //如果状态是失败，就调用失败回调
            }else if(this.status === REJECTED){
                //失败时，传入reason,未传值则忽略
                //改成异步原理同上
                setTimeout(()=>{
                    try{
                        //下一个MyPromsie的回调函数给的是当前的reason
                        let params = failCallBack(this.reason)
                        resolveMyPromise(promise1,params,resolve,reject)
                    }catch(e){
                        reject(e)
                    }
                })
            }else{
                //异步执行会进入这里，当状态为pending时，不执行回调函数，当状态确定时再执行
                //需要把两个回调函数缓存到对象中,用缓存模拟异步
                //由于上面处理的都是异步数据，所以要把回调方法都封装到异步函数中
                this.sucsessCallBack.push(()=>{
                    //把整个异步解析封装到一个方法中，然后把这方法放到数组中缓存起来，用到的时候再直接执行即可
                    setTimeout(() => {
                        try{
                            let params = sucsessCallBack(this.value)
                            resolveMyPromise(promise1,params,resolve,reject)
                        }catch(e){
                            reject(e)
                        }
                    });
                })
                //同上
                this.failCallBack = ()=>{
                    //失败回调不需要数组，因为有多个错误的时候也只返回第一个失败回调
                    setTimeout(() => {
                        //try/catch要和失败回调一起放入setTimeout中，不然是同步执行，捕获不到回调函数中的错误
                        try{
                            let params = failCallBack(this.reason)
                            resolveMyPromise(promise1,params,resolve,reject)
                        }catch(e){
                            reject(e)
                        }
                    });
                }
            }
        })
        return promise1
    }
    //定义MyPromise对象的catch方法,接收一个回调函数
    catch(callback){
        //相当于调用了当前对象的then方法，但只传了第二个参数，this就是调用catch的对象
        return this.then(undefined,callback)
    }
    //定义finally方法，finally方法是在对象上调用，并且无论当前对象是resolve还是reject都会执行，
    //然后返回一个MyPromise对象，支持异步等待
    finally(callback){
        //并继续向下传递之前的结果
        // return this.then((value)=>{
        //     callback()
        //     return value
        // },(reason)=>{
        //     callback()
        //     //这里不要忘了用throw才会抛出异常，走reject否则会走resolve
        //     throw reason
        // })
        //让finally之后的then可以等待finally中可以执行的异步代码
        return this.then((value)=>{
            //因为callback()返回的是一个MyPromise,所以MyPromise.resolve(callback())返回的就是这个MyPromise
            //这个MP执行then的时候是pending，然后把()=>value缓存到successCallBack中，所以等异步时间到了resolve
            //再执行这个then的()=>value
            return MyPromise.resolve(callback()).then(()=>value)
        },(reason)=>{
            return MyPromise.resolve(callback()).then(()=>{throw reason})
        })
    }
    //定义静态方法resolve
    static resolve(value){
        //如果value是MyPromise对象就直接返回
        if(value instanceof MyPromise) return value
        return new MyPromise((resolve,reject)=>{
            resolve(value)
        })
    }
    //定义静态方法reject
    static reject(reason){
        //如果value是MyPromise对象就直接返回
        if(reason instanceof MyPromise) return reason
        return new MyPromise((resolve,reject)=>{
            reject(reason)
        })
    }
    //定义静态all方法
    //all方法参数是一个数组
    static all(arr){
        //添加的次数
        let index = 0
        return new MyPromise((resolve,reject)=>{
            if(!(arr instanceof Array)){
                throw "需要一个数组"
            }else if(!arr.length){
                resolve([])
            }
            let res = []
            //因为arr中的对象可能是异步的，所以要放在里面
            for(let i=0;i<arr.length;i++){
                if(arr[i] instanceof MyPromise) {
                    arr[i].then((value)=>{
                        //这里赋值要用索引，不能用push，会串位置
                        res[i] = value
                        index++
                        //次数等于数组长度时，解决数组,需要放在then里面，每次都计算，
                        //放到外面犹豫异步只计算一次的话不会满足条件
                        if(res.length == index){
                            resolve(res)
                        }
                    },(reason)=>{
                        //如果任意元素抛出异常，立刻结束循环，并拒绝该错误信息
                        reject(reason)
                    })
                }else{
                    //公共代码可以提取到外面
                    res[i] = arr[i]
                    index++
                    //次数等于数组长度时，解决数组
                    if(res.length == index){
                        resolve(res)
                    }
                }
            }
        })
    }
    //尝试race方法
    static race(arr){
        return new MyPromise((resolve,reject)=>{
            if(!(arr instanceof Array)){
                throw "需要一个数组"
            }else if(!arr.length){
                //这里与all不同
                return
            }
            for(let i=0;i<arr.length;i++){
                if(arr[i] instanceof MyPromise) {
                    //无论解决还是绝都立即兑现
                    arr[i].then((value)=>{
                        resolve(value)
                    },(reason)=>{
                        reject(reason)
                    })
                }else{
                    resolve(arr[i])
                }
            }
        })
    }
}
//放在外面，在同一个MyPromise对象多次调用then时，只创建这一个对象，节省空间
function resolveMyPromise (promise,params,resolve,reject) {
    //如果MyPromise对象是自己
    if(promise === params){
        //直接reject个错误,并返回
        return reject(new TypeError("不能是自己"))
    }
    //如果params是一个新的MyPromise对象，则返回他的resolve结果
    if(params instanceof MyPromise){
        params.then((value)=>{
            resolve(value)
        },(err)=>{
            reject(err)
        })
    }else{
        //否则返回的是其他数据
        resolve(params)
    }
}
module.exports = MyPromise